package it.dk.view;


import it.dk.model.AlertCollisionBarrelLeftBorder;
import it.dk.model.AlertCollisionBarrelPlatform;
import it.dk.model.AlertCollisionHero;
import it.dk.model.Barrel;
import it.dk.model.CollisionManager;
import it.dk.model.Hero;
import it.dk.model.HeroState;
import it.dk.model.HeroStateBase;
import it.dk.model.HeroStateDownMovement;
import it.dk.model.HeroStateJumpMovement;
import it.dk.model.HeroStateLeftMovement;
import it.dk.model.HeroStateRightMovement;
import it.dk.model.HeroStateUpMovement;

import java.util.ArrayList;
import java.util.Observable;
import java.util.Observer;
import java.util.Random;

import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Input;

/**
 * Classe che rappresenta lo stato di partita in corso.
 * 
 * Pattern utilizzato: State
 */
public class GameStatePlaying extends GameState implements Observer {

	/* Costante di massimo e minimo ritardo per la generazione dei barili */
	private final float MAX_DELAY = 4.5f;
	private final float MIN_DELAY = 1.5f;
	protected BoardRender boardRender;
	protected GameSound soundManager;
	/* Generatore secondi random usato per la generazione di barili */
	private Random random;
	/* Tempo di ritardo per la generazione del prossimo barile */
	private float barrelDelay;

	protected boolean jumping;
	private boolean flyingOnTheBorderline;
	private boolean earnedPoints;

	// oggetti che collidono
	protected int collisionHeroPlatform;
	protected int collisionHeroPlatformUpBorder;
	protected int collisionHeroBarrel;
	protected int collisionHeroLadder;
	protected int collisionHeroTopLadder;
	protected int collisionHeroBottomLadder;
	protected int collisionHeroLeftBorder;
	protected int collisionHeroRightBorder;
	private int collisionHeroPrincess;
	private int collisionHeroPointGate;
	private ArrayList<Integer> collisionBarrelPlatform;
	private ArrayList<Integer> collisionBarrelBorder;
	private boolean hasChanged;
	private boolean topLadderJumping = false;

	// oggetto eroe
	protected Hero hero;
	protected HeroState s;

	/**
	 * Costruttore della classe
	 * 
	 * @param board	riferimento alla board di gioco
	 */
	public GameStatePlaying(DonkeyKong board) {
		super(board);
		board.getGame().getCollisionManager().getAcHero().addObserver(this);
		board.getGame().getCollisionManager().getAcBarrelPlatform().addObserver(this);
		board.getGame().getCollisionManager().getAcBarrelBorder().addObserver(this);

		collisionBarrelPlatform = new ArrayList<Integer>();
		for (int i = 0; i < board.getGame().getMaxBarrels(); i++)
			collisionBarrelPlatform.add(CollisionManager.getNotCollision());

		collisionBarrelBorder = new ArrayList<Integer>();
		for (int i = 0; i < board.getGame().getMaxBarrels(); i++)
			collisionBarrelBorder.add(CollisionManager.getNotCollision());

		random = new Random();
		barrelDelay = 0;

		jumping = false;
		flyingOnTheBorderline = false;
		earnedPoints = false;
		hasChanged = false;

		soundManager = GameSound.getInstance();
		soundManager.playSoundtrack();
	}

	/**
	 * Metodo che viene richiamato da Board per l'aggiornamento della
	 * logica del gico, come posizione dei barili e dell'hero in base
	 * alla pressione dei tasti da parte dell'utente
	 * 
	 * @param container	oggetto di tipo GameContainer che rappresenta la finestra contenente il gioco
	 * @param delta		periodo di aggiornamento
	 */
	public void update(GameContainer container, int delta) {
		hero = this.board.getGame().getHero();

		/*
		 * Generatore di barili con cadenza pseudorandom, in numero minore di
		 * MAX_BARRELS
		 */
		barrelGenerator(delta);

		/* Ciclo di aggiornamento dello stato delle collisioni */
		board.getGame().getCollisionManager().update();

		//System.out.println("plat"+collisionHeroPlatform+"/plat_up"+collisionHeroPlatformUpBorder+"/barr"+collisionHeroBarrel+"/ladd"+collisionHeroLadder+"/ladd_top"+collisionHeroTopLadder+"/ladd_bot"+collisionHeroBottomLadder+"/bor_l"+collisionHeroLeftBorder+"/bor_r"+collisionHeroRightBorder);

		/* METODO PER IL CONTROLLO COLLISIONI */
		collisionDetector();

		/* CONTROLLO INPUT UTENTE */
		movementDetector(container,delta);
	}

	/**
	 * Metodo che controlla le collisioni rilevate e genera
	 * gli opportuni nuovi stati dell'eroe
	 */
	protected void collisionDetector()  {

		/* CONTROLLO COLLISIONI */
		// Assegna un nuovo stato solo se:
		// 1) c'è un cambio di stato.
		// 2) sto saltando, e ho bisogno che si controlli ogni volta l'evoluzione durante il salto.
		if(hasChanged || jumping){

			if(collisionHeroPointGate != CollisionManager.getNotCollision() && jumping){
				// EROE GUADAGNA PUNTI
				if(earnedPoints == false){
					board.increasePoints();
					earnedPoints = true;
					soundManager.playJumpOverObstacle();
				}
			}else
				earnedPoints = false;

			hasChanged = false;
			if(collisionHeroBarrel != CollisionManager.getNotCollision()){
				// EROE MUORE
				soundManager.stopSoundtrack();
				board.heroLose();
			}
			else if(collisionHeroPrincess != CollisionManager.getNotCollision()){
				// EROE VINCE
				soundManager.stopSoundtrack();
				board.heroWin();
			}
			else{
				s = new HeroStateBase(hero);

				// CONDIZIONE SALTO SU SCALA CHE SCENDE
				// eri su una scala, hai fatto un salto e ora tocchi la piattaforma
				if(topLadderJumping && jumping && collisionHeroPlatform != CollisionManager.getNotCollision()){
					topLadderJumping = false;
					jumping = false;
					heroOnPlatformCorrection();
				}
				// L'erore � coi piedi nella piattaforma (collide con piattaforma ma non col suo bordo superiore)
				// => non sta saltando, quindi jumping = false
				// => non posso saltare quindi non abilito il salto (e nessun altro movimento)
				else if(collisionHeroPlatform != CollisionManager.getNotCollision() && collisionHeroPlatformUpBorder == CollisionManager.getNotCollision()){
					jumping = false;
					//System.out.println("Sto coi piedi nella piattaforma " + jumping);
				}
				// L'eroe collide solo con la scala
				// => non sta saltando per� non posso impostare jumping = false altrimenti
				//    in caso di salto parabolico attravreso la scala, hero rimane appeso
				else if(collisionHeroLadder != CollisionManager.getNotCollision() || collisionHeroRightBorder != CollisionManager.getNotCollision() || collisionHeroLeftBorder != CollisionManager.getNotCollision()){
					//System.out.println("Sto solo sulla scala");
				}
				else{
					// altrimenti decoro lo stato col salto
					s = new HeroStateJumpMovement(s);
				}

				// Collido con il bordo superiore della piattaforma
				if(collisionHeroPlatformUpBorder != CollisionManager.getNotCollision()){
					jumping = false;
					//System.out.println("collido piattaforma " + jumping);


					// non collido con bordo sinistro
					if(collisionHeroLeftBorder == CollisionManager.getNotCollision())
						s = new HeroStateLeftMovement(s);
					//else
					//System.out.println("collido piattaforma e bordo sinistro");


					// non collido con bordo destro
					if(collisionHeroRightBorder == CollisionManager.getNotCollision())
						s = new HeroStateRightMovement(s);
					//else
					//System.out.println("collido piattaforma e bordo destro");


					// collido con scala che scende
					if(collisionHeroTopLadder != CollisionManager.getNotCollision()){
						heroOnPlatformCorrection();
						//System.out.println("collido piattaforma e scala che scende " + jumping);
						s = new HeroStateDownMovement(new HeroStateJumpMovement(s));
						topLadderJumping = true;
					}

					// collido con scala che sale
					else if(collisionHeroBottomLadder != CollisionManager.getNotCollision()){
						heroOnPlatformCorrection();
						//System.out.println("collido piattaforma e scala che sale " + jumping);
						s = new HeroStateUpMovement(new HeroStateJumpMovement(s));
					}

					// se non collide con scala, riposiziona l'eroe su piattaforma
					else{
						// poichè tocco con la parte superiore piattaforma
						// e poichè sicuramente non tocco bordi di scale
						// se non tocco neanche il centro delle scale, o il centro della piattaforma, abilito il salto.
						heroOnPlatformCorrection();
					}
				}
				// se non collide con scala, e collide con la parte interna della piattaforma, riposiziona l'eroe su piattaforma
				else if(collisionHeroPlatform != CollisionManager.getNotCollision() && collisionHeroLadder == CollisionManager.getNotCollision()){
					heroOnPlatformCorrection();
					jumping = false;
				}

				// collido solo con scala e non con parte superiore della piattaforma
				if(collisionHeroLadder != CollisionManager.getNotCollision() && collisionHeroPlatformUpBorder == CollisionManager.getNotCollision()){
					//jumping = false; commentato senn� si appende alla scala
					flyingOnTheBorderline = false;

					//System.out.println("collido solo con scala " + jumping);
					s = new HeroStateUpMovement(new HeroStateDownMovement(s));
				}

				// se non collido ne con scale ne con piattaforme
				// (includiamo anche il caso di salto parabolico con collisione dei bordi)
				if(collisionHeroLadder == CollisionManager.getNotCollision() && collisionHeroPlatform == CollisionManager.getNotCollision()){
					jumping = true;

					// collido con bordo sinistro o con bordo destro
					if(collisionHeroLeftBorder != CollisionManager.getNotCollision() || collisionHeroRightBorder != CollisionManager.getNotCollision())
						flyingOnTheBorderline = true;
					else
						flyingOnTheBorderline = false;

					s = new HeroStateDownMovement(s);
					//System.out.println("Sto volandoooo " + jumping);
				}

				s.setJumping(jumping);
				s.setJumpOnTheBorderline(flyingOnTheBorderline);
				hero.setState(s);
			}
		}
	}

	/**
	 * Metodo che gestisce l'update dell'eroe coerentemente
	 * con la movimentazione ricevuta da tastiera
	 * 
	 * @param container	oggetto di tipo GameContainer che rappresenta la finestra contenente il gioco
	 * @param delta		periodo di aggiornamento
	 */
	protected void movementDetector(GameContainer container, int delta){
		Input input = container.getInput();

		if(jumping){
			hero.moveDown(delta);
			//System.out.println("plat"+collisionHeroPlatform+"/plat_up"+collisionHeroPlatformUpBorder+"/barr"+collisionHeroBarrel+"/ladd"+collisionHeroLadder+"/ladd_top"+collisionHeroTopLadder+"/ladd_bot"+collisionHeroBottomLadder+"/bor_l"+collisionHeroLeftBorder+"/bor_r"+collisionHeroRightBorder);
		}else if(input.isKeyDown(Input.KEY_SPACE) && input.isKeyDown(Input.KEY_LEFT)){
			jumping = true;
			hero.jump(delta,-1);
			if(collisionHeroRightBorder == CollisionManager.getNotCollision() && collisionHeroLeftBorder == CollisionManager.getNotCollision())
				soundManager.playJump();
		}else if(input.isKeyDown(Input.KEY_SPACE) && input.isKeyDown(Input.KEY_RIGHT)){
			jumping = true;
			hero.jump(delta,1);
			if(collisionHeroRightBorder == CollisionManager.getNotCollision() && collisionHeroLeftBorder == CollisionManager.getNotCollision())
				soundManager.playJump();
		}else if(input.isKeyDown(Input.KEY_SPACE)){
			jumping = true;
			hero.jump(delta,0);
			if(collisionHeroRightBorder == CollisionManager.getNotCollision() && collisionHeroLeftBorder == CollisionManager.getNotCollision() && collisionHeroLadder == CollisionManager.getNotCollision())
				soundManager.playJump();
		}else if (input.isKeyDown(Input.KEY_LEFT)){
			hero.moveLeft(delta);
			if(collisionHeroPlatformUpBorder != CollisionManager.getNotCollision())
				soundManager.playWalk();
		}else if (input.isKeyDown(Input.KEY_RIGHT)){
			hero.moveRight(delta);
			if(collisionHeroPlatformUpBorder != CollisionManager.getNotCollision())
				soundManager.playWalk();
		}else if(input.isKeyDown(Input.KEY_UP)){
			hero.moveUp(delta);
		}else if (input.isKeyDown(Input.KEY_DOWN)){
			hero.moveDown(delta);
		}else
			soundManager.stopWalk();
	}


	/**
	 * Metodo che effettua la correzione della posizione dell'hero
	 * rispetto alla piattaforma
	 */
	private void heroOnPlatformCorrection(){
		// Controllo la posizione dell'eroe risolvendo problemi relativi alla posizione relativa rispetto alle piattaforme
		if(collisionHeroPlatform != CollisionManager.getNotCollision()){
			this.board.getGame().getHero().setActualSpeedY(0);
			this.board.getGame().getHero().getShape().setY(board.getGame().getPlatforms().get(collisionHeroPlatform).getShape().getY() - board.getGame().getHeroHeight());
			this.board.getGame().getHero().getInnerShape().setY(board.getGame().getPlatforms().get(collisionHeroPlatform).getShape().getY() - board.getGame().getHeroInnerShapeHeight());

			collisionHeroPlatformUpBorder = collisionHeroPlatform;
			collisionHeroLadder = CollisionManager.getNotCollision();
		}
	}

	/**
	 * Metodo che si occupa della generazione pseudocasuale dei barili
	 * 
	 * @param delta		periodo di aggiornamento
	 */
	protected void barrelGenerator(int delta) {

		float delay = barrelDelay - (board.getLevel()/5);

		if ((System.currentTimeMillis() - board.getGame()
				.getLastBarrelGenerationTime()) / 1000.0 >= delay) {

			board.getGame().activateBarrel();

			float tempTime = random.nextFloat()*(MAX_DELAY - MIN_DELAY) + MIN_DELAY;
			/*
			 * aggiornamento del tempo di ritardo nella generazione del nuovo
			 * barile
			 */
			barrelDelay = (tempTime < MIN_DELAY) ? MIN_DELAY : tempTime;
		}

		/*
		 * ciclo per aggiornare i movimenti dei barili in base alla piattaforma
		 * sulla quale si muovono
		 */
		for (int i = 0; i < collisionBarrelPlatform.size(); i++) {

			if (i < board.getGame().getBarrels().size()) {
				Barrel barrel = board.getGame().getBarrels().get(i);

				if(barrel.isActivated()){
					int platformIndex = collisionBarrelPlatform.get(i);

					if(platformIndex!=CollisionManager.getNotCollision()){
						/* Setto la y della shape del barile in modo da posizionarlo correttamente sulla piattaforma */
						barrel.getShape().setY(board.getGame().getPlatforms().get(platformIndex).getShape().getY() - board.getGame().getBarrelSize());
						barrel.getRound().setY(board.getGame().getPlatforms().get(platformIndex).getShape().getY() - board.getGame().getBarrelSize());
						barrel.getUpperGate().setY(board.getGame().getPlatforms().get(platformIndex).getShape().getY() - board.getGame().getBarrels().get(0).getShape().getHeight() - board.getGame().getBarrels().get(0).getUpperGate().getHeight());

						if(platformIndex%2==0){
							barrel.moveLeft(delta);
						}else{
							barrel.moveRight(delta);
						}
					} else {
						barrel.moveDown(delta);
					}
				}
			}
		}

		/* Controllo che un generico barile abbia colliso con il bordo sinistro */
		for (int i = 0; i < collisionBarrelBorder.size(); i++) {

			if (i < board.getGame().getBarrels().size()
					&& collisionBarrelBorder.get(i) != CollisionManager.getNotCollision()) {
				board.getGame().resetBarrelPosition(i);
			}
		}
	}

	/**
	 * Metodo che viene richiamato da Board per
	 * il render degli oggetti grafici
	 * 
	 * @param container	oggetto di tipo GameContainer che rappresenta la finestra contenente il gioco
	 * @param g			oggetto di tipo Graphics che consente la renderizzazione di un generioco oggetto grafico
	 */
	public void render(GameContainer container, Graphics g) {

		hero = Hero.getInstance();
		boardRender = BoardRender.getInstance();
		boardRender.renderScene(g, board.getHeroLifes(), board.getGameLevel(), board.getGamePoints());

		renderMovement(container, g);

	}

	/**
	 * Metodo che gestisce il render dell'eroe coerentemente
	 * con la movimentazione ricevuta da tastiera
	 * 
	 * @param container	oggetto di tipo GameContainer che rappresenta la finestra contenente il gioco
	 * @param g			oggetto di tipo Graphics che consente la renderizzazione di un generioco oggetto grafico
	 */
	protected void renderMovement(GameContainer container, Graphics g){
		Input input = container.getInput();

		if(input.isKeyDown(Input.KEY_SPACE) && input.isKeyDown(Input.KEY_LEFT)){
			if(collisionHeroRightBorder != CollisionManager.getNotCollision() || collisionHeroLeftBorder != CollisionManager.getNotCollision())
				boardRender.renderHeroStopped(g);
			else
				boardRender.renderHeroJumpingLeft(g);
		}
		else if(input.isKeyDown(Input.KEY_SPACE) && input.isKeyDown(Input.KEY_RIGHT)){
			if(collisionHeroRightBorder != CollisionManager.getNotCollision() || collisionHeroLeftBorder != CollisionManager.getNotCollision())
				boardRender.renderHeroStopped(g);
			else
				boardRender.renderHeroJumpingRight(g);
		}
		else if(jumping || input.isKeyDown(Input.KEY_SPACE)){
			if(collisionHeroRightBorder != CollisionManager.getNotCollision() || collisionHeroLeftBorder != CollisionManager.getNotCollision())
				boardRender.renderHeroStopped(g);
			else
				boardRender.renderHeroJumping(g);
		} else if (input.isKeyDown(Input.KEY_LEFT)) {
			if(collisionHeroLadder != CollisionManager.getNotCollision() && collisionHeroPlatformUpBorder == CollisionManager.getNotCollision())
				boardRender.renderHeroClimbing(g);
			else
				boardRender.renderHeroRunningLeft(g);
		} else if (input.isKeyDown(Input.KEY_RIGHT)) {
			if(collisionHeroLadder != CollisionManager.getNotCollision() && collisionHeroPlatformUpBorder == CollisionManager.getNotCollision())
				boardRender.renderHeroClimbing(g);
			else
				boardRender.renderHeroRunningRight(g);
		}
		else if (input.isKeyDown(Input.KEY_UP)) {
			if(collisionHeroLadder != CollisionManager.getNotCollision() && collisionHeroPlatformUpBorder == CollisionManager.getNotCollision() && collisionHeroTopLadder == CollisionManager.getNotCollision())
				boardRender.renderHeroClimbing(g);
			else
				boardRender.renderHeroStopped(g);
		} else if (input.isKeyDown(Input.KEY_DOWN)) {
			if(collisionHeroLadder != CollisionManager.getNotCollision() && collisionHeroPlatformUpBorder == CollisionManager.getNotCollision() && collisionHeroBottomLadder == CollisionManager.getNotCollision())
				boardRender.renderHeroClimbing(g);
			else
				boardRender.renderHeroStopped(g);

		} else {
			if(collisionHeroLadder != CollisionManager.getNotCollision()  && collisionHeroPlatformUpBorder == CollisionManager.getNotCollision())
				boardRender.renderHeroClimbing(g);
			else
				boardRender.renderHeroStopped(g);
		}	
	}

	/**
	 * Metodo che rileva il rilascio di un tasto
	 * 
	 * @param key	codice del tasto rilasciato
	 */
	public void keyReleased(int key, char c) {
		/* per singoli click */
	}

	/**
	 * Metodo che si occupa di aggiornare lo stato delle collisioni
	 * AlertCollisionHero, AlertCollisionBarrelPlatform e
	 * AlertCollisionBarrelLeftBorder. In base all'oggetto Observable "o",
	 * che ha scatenato l'aggiornamento dello stato, viene riconosciuta
	 * la giusta collisione e aggiornato il relativo vettore delle collisioni.
	 * 
	 * @param o		oggetto di tipo Observable che ha scatenato l'aggiornamento dello stato
	 * @param arg
	 */
	public void update(Observable o, Object arg) {
		AlertCollisionHero ac;
		AlertCollisionBarrelPlatform acBP;
		AlertCollisionBarrelLeftBorder acBL;
		if (o.getClass().equals(AlertCollisionHero.class)) {
			ac = (AlertCollisionHero) o;
			collisionHeroPlatform = ac.getCollision().get(
					CollisionManager.getIndexPlatformPosition());
			collisionHeroPlatformUpBorder = ac.getCollision().get(
					CollisionManager.getIndexPlatformUpPosition());
			collisionHeroBarrel = ac.getCollision().get(
					CollisionManager.getIndexBarrelPosition());
			collisionHeroLadder = ac.getCollision().get(
					CollisionManager.getIndexLadderPosition());
			collisionHeroLeftBorder = ac.getCollision().get(
					CollisionManager.getIndexBorderLeftPosition());
			collisionHeroRightBorder = ac.getCollision().get(
					CollisionManager.getIndexBorderRightPosition());
			collisionHeroTopLadder = ac.getCollision().get(
					CollisionManager.getIndexLadderUpPosition());
			collisionHeroBottomLadder = ac.getCollision().get(
					CollisionManager.getIndexLadderDownPosition());
			collisionHeroPrincess = ac.getCollision().get(
					CollisionManager.getIndexPrincess());
			collisionHeroPointGate = ac.getCollision().get(
					CollisionManager.getIndexPointGate());
			hasChanged = true;
		} else if (o.getClass().equals(AlertCollisionBarrelPlatform.class)) {
			acBP = (AlertCollisionBarrelPlatform) o;
			collisionBarrelPlatform = (acBP.getCollision());
		} else if (o.getClass().equals(AlertCollisionBarrelLeftBorder.class)) {
			acBL = (AlertCollisionBarrelLeftBorder) o;
			collisionBarrelBorder = (acBL.getCollision());
		}
	}
}
